<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="description" content="">
  <meta id="viewport" name="viewport" content="width=device-width, initial-scale=1">
  <title>WebSocket</title>
  <style>
      div {
        padding: 10px;
      }
      #status, #reconnect {
        color: white;
      }
      .connecting {
        background-color: #1B9AF7;
      }

      .connectimeout {
        background-color: #FEAE1B;
      }

      .connected {
        background-color: #A5DE37;
      }
      .disconnected {
        background-color: #FF4351;
      }

      .reconnecting {
        background-color: #7B72E9;
      }

      .hide {
        display: none;
      }
  </style>
</head>

<body>

  <div id='status' class='connecting'>Connecting...</div>
  <div id='reconnect' class='hide'></div>
  <div id="result"></div>

  <script>
    const isHttps = location.protocol.startsWith('https')

    var wsHeartBeat;
    var modules = {};
    var reconnectTimer;
    var reconnectTimeout = 20000;
    var heartBeatTimeout = 20000;

    const exists = [
      "79c8f3c75125442db471d15f8fc7d405e1337a0ff4d0496391e621566f9b2817_1573120345925",
      "26d9ca3c7d5545828f2aec553a709884a7130fe2f7044abb9b1bff1b21cc0ff8_1573120345974",
      "c0ac81be83ba48a2adf82bb07f6da082f19590f96aef4a739354cc91fb02bfcb_1573120346017",
      "57342f3d12d74a4887605b99776c7f01b9b4f645a25a4b379ddc79a6659957ed_1573120346052",
      "828050481ce34fe082a96393a2f8f4b7f19590f96aef4a739354cc91fb02bfcb_1573120346085",
      "e4d82562c0ad4dfca7612f8c7573fc7dd615674bfda247db8eac114ce46489eb_1573120346105",
      "376259f70c3d48f8911e09ee927c119bb0c287a9c03242c28a4b374900b2494d_1573120346131",
      "76f85af38f1143a5b2ac65ec9ede0f6251030f2fad954ae0ae6ea24cfe1b473c_1573120346180",
      "308175cd21654cdf923bc8a39375fdbba4ca3b05436643969807d11ba33b42d7_1573120346210",
      "6e669ea9e1ff48bfb72de5efe07c6a3e3c88a3b9499a4f84b301480315232874_1573120346215",
      "97868d5e606b4ca5a80f0c80151d0b6astoreset000000000000000000000000_1573120346227",
      "e02c7c7be1494e6481d6ab00e8ed346astoreset000000000000000000000000_1573120346231",
      "d9ed12b6fd7e4820ad708bf0eaf7ff9astoreset000000000000000000000000_1573120346241",
      "bd811f824e2543bbb98bd534c1e3272estoreset000000000000000000000000_1573120346261",
      "ad269e69d48a4c3e918e8242985a0781storeset000000000000000000000000_1573120346270",
      "865e28d93ac84afda6c8922e6372a5f2storeset000000000000000000000000_1573120346275",
      "185864f024c64a3bbdf1d8df0194c93bstoreset000000000000000000000000_1573120346279",
      "e23842ba7c324f3baf36d36fc3686e04storeset000000000000000000000000_1573120346292",
      "6f1bc730f7df4d499aebaeb1818b3c18storeset000000000000000000000000_1573120346298",
      "e27a9952305248f0befb72eecc708b81storeset000000000000000000000000_1573120346303",
      "6745927edea543f19f732d7c0d9a5f1dstoreset000000000000000000000000_1573120346308",
      "853669c646a848929bada0bb235d0ff2storeset000000000000000000000000_1573120346312",
      "719e091264854cfebd0783d12b8d7c1cstoreset000000000000000000000000_1573120346328",
      "5050e9b3562943cfa713a96a7b5a4087storeset000000000000000000000000_1573120346339",
      "912890c0e0674c678f1d4c6b1d39d389058844cc52e349b7ab3f013aa9dee2d3_1573120346983",
      "8e46110a149a41c58929698fc44288534a8b26e20a2b4e1c97ee5319b13bed0a_1573120347279",
      "1c1c5516731a46248667dd07608f3fd93c31ac3326d241e9933c49ef03b51a97_1573120347434",
      "5398cc824de7487e9cd5e7d5e5abdd76958f39c348b34c439993ed4b308ffd47_1573120347749",
      "fbd4d72955f64aecbc994b3d5b7d855d1a60f05dc7474e6e8cc2660acf86e620_1573120348721",
      "1ddec0f5a77648e295193f7f0ac08f8533b798a50fdd4c12a817e544ee4f8351_1573120349047",
      "d54d2033db244b7ba814f4f2bc82e3283c88a3b9499a4f84b301480315232874_1573120349207",
      "a48dd5274bcd47db803ed293d3c2f7ee94a0c4ace9be4515b94ecf0f9f1a1512_1573120349527"
    ]

    wsHeartBeat = heartBeat(modules)
    
    function connect() {
      var ws = new WebSocket(`${isHttps ? 'wss' : 'ws'}://${location.host}/ws`);
      modules.ws = ws;

      ws.onopen = (event) => {
        console.info("WebSocket is open now.", event);

        document.getElementById('status').innerHTML = 'Connected';
        document.getElementById('status').className = 'connected';
        document.getElementById('reconnect').className = 'hide';

        if (reconnectTimer) {
          clearTimeout(reconnectTimer)
          reconnectTimer = undefined;
          document.getElementById('reconnect').className = 'hide'
          document.getElementById('reconnect').innerHTML = ''
        }

        ws.onmessage = (event) => {
          if (event.data === '2') {
            wsHeartBeat.reset()
            wsHeartBeat.start()
            return
          }
          const data = decodeURIComponent(event.data.replace(/\+/g, "%20"))
          let decodeData = JSON.parse(data); //转后端编码过的数据
          wsHeartBeat.updateUglyWait(decodeData.sequence)

          document.getElementById('result').innerHTML = decodeData.time
        }

        exists.forEach(sequence => {

          const data = {
            "listener": "add",
            "id": sequence.substring(32, 64),
            "sequence": sequence,
            "filterStr": {"requiredFields":["id","alert"]},
            "windowId": "0568b955a137452e956e9d5768717ca9",
            "widgetType": "chart",
            "mode": "edit",
            "chartType": "bar",
            "windowName": "领航员"
          }
          
          ws.send(JSON.stringify(data))
          wsHeartBeat.setUglyWait(sequence, 10000)
        })

        wsHeartBeat.reset()
        wsHeartBeat.start()
      }

      ws.onclose = (event) => {
        console.info("WebSocket is closed now.", event);

        document.getElementById('status').innerHTML = 'Disconnected';
        document.getElementById('status').className = 'disconnected';
      }

      ws.onerror = (event) => {
        console.error("WebSocket error observed:", event);
      }

      return ws;
    }

    function reconnect() {
      var ws = connect()

      var reconnectElem = document.createElement('div')
      reconnectElem.className = 'reconnecting'
      reconnectElem.innerHTML = 'Trying to reconnect...'
      document.getElementById('reconnect').appendChild(reconnectElem)
      document.getElementById('reconnect').className = ''

      // reconnectTimer = setTimeout(function() {
      //   ws.close(4001, 'Connect timeout and closed by client xbb');//这个client端是收不到的
      //   reconnectElem.className = 'connectimeout'
      //   reconnectElem.innerHTML = 'Timeout'
      //   reconnect()
      // }, reconnectTimeout)
    }

    var ws = connect()
    reconnectTimer = setTimeout(function() {
      ws.close(4001, 'Connect timeout and closed by client xbb');
      reconnect()
    }, reconnectTimeout)

    function heartBeat(modules) {
      var timer;
      var interval;

      var uglyWait = {}

      function heartBeatReconnect() {
        if (modules.ws.readyState !== WebSocket.OPEN) {
          modules.ws.close(4001, 'Pong timeout and closed by client xbb')
          reconnect()
          clearUglyWait()
        } else {
          modules.ws.send('1')
        }
      }

      function timeout() {
        interval = setInterval(function() {
          uglyTest()
          heartBeatReconnect();
        }, heartBeatTimeout)
      }

      function uglyTest() {
        const ret = checkUglyWait();
        if (ret) {
          const message = `spend ${ret.spend}ms waiting ${ret.sequence} over ${ret.timeout}ms.`;
          console.warn(message);
          //message不能超过123bytes
          modules.ws.close(4002, message)
        }
      }

      function checkUglyWait() {
        const now = Date.now();
        for (const sequence in uglyWait) {
          const spend = now - uglyWait[sequence].lastTime;
          const timeout = Math.max(120000, 5 * uglyWait[sequence].timeout);
          if (spend > timeout) {
            return {
              sequence: sequence,
              spend: spend,
              timeout: uglyWait[sequence].timeout
            }
          }
        }
        return null;
      }

      function setUglyWait(sequence, timeout) {
        uglyWait[sequence] = { lastTime: Date.now(), timeout: timeout }
      }

      function updateUglyWait(sequence) {
        if (uglyWait[sequence]) {
          uglyWait[sequence].lastTime = Date.now();
        }
      }

      function removeUglyWait(sequence) {
        delete uglyWait[sequence]
      }

      function clearUglyWait() {
        uglyWait = {}
      }

      function start() {
        setTimeout(function() {
          modules.ws.send('1')

          uglyTest()

          timer = setTimeout(function() {
            timeout();
          }, heartBeatTimeout)

        }, heartBeatTimeout)
      }

      function reset() {
        clearTimeout(timer)
        clearInterval(interval)
      }

      return {
        start, reset, setUglyWait, updateUglyWait, removeUglyWait, clearUglyWait
      }
    }
    
  </script>
</body>

</html>
